package com.fsck.k9.activity.compose

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.view.Menu
import androidx.core.content.ContextCompat
import androidx.loader.app.LoaderManager
import com.fsck.k9.Account
import com.fsck.k9.Identity
import com.fsck.k9.K9
import com.fsck.k9.activity.compose.ComposeCryptoStatus.AttachErrorState
import com.fsck.k9.activity.compose.ComposeCryptoStatus.SendErrorState
import com.fsck.k9.autocrypt.AutocryptDraftStateHeader
import com.fsck.k9.autocrypt.AutocryptDraftStateHeaderParser
import com.fsck.k9.helper.Contacts
import com.fsck.k9.helper.MailTo
import com.fsck.k9.helper.ReplyToParser
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.Message.RecipientType
import com.fsck.k9.message.AutocryptStatusInteractor
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatus
import com.fsck.k9.message.ComposePgpEnableByDefaultDecider
import com.fsck.k9.message.ComposePgpInlineDecider
import com.fsck.k9.message.MessageBuilder
import com.fsck.k9.message.PgpMessageBuilder
import com.fsck.k9.ui.R
import com.fsck.k9.view.RecipientSelectView.Recipient
import org.openintents.openpgp.OpenPgpApiManager
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderError
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState
import timber.log.Timber

private const val STATE_KEY_CC_SHOWN = "state:ccShown"
private const val STATE_KEY_BCC_SHOWN = "state:bccShown"
private const val STATE_KEY_LAST_FOCUSED_TYPE = "state:lastFocusedType"
private const val STATE_KEY_CURRENT_CRYPTO_MODE = "state:currentCryptoMode"
private const val STATE_KEY_CRYPTO_ENABLE_PGP_INLINE = "state:cryptoEnablePgpInline"

private const val CONTACT_PICKER_TO = 1
private const val CONTACT_PICKER_CC = 2
private const val CONTACT_PICKER_BCC = 3
private const val OPENPGP_USER_INTERACTION = 4
private const val REQUEST_CODE_AUTOCRYPT = 5

private const val PGP_DIALOG_DISPLAY_THRESHOLD = 2

class RecipientPresenter(
    private val context: Context,
    loaderManager: LoaderManager,
    private val openPgpApiManager: OpenPgpApiManager,
    private val recipientMvpView: RecipientMvpView,
    account: Account,
    private val composePgpInlineDecider: ComposePgpInlineDecider,
    private val composePgpEnableByDefaultDecider: ComposePgpEnableByDefaultDecider,
    private val autocryptStatusInteractor: AutocryptStatusInteractor,
    private val replyToParser: ReplyToParser,
    private val draftStateHeaderParser: AutocryptDraftStateHeaderParser
) {
    private lateinit var account: Account
    private var alwaysBccAddresses: Array<Address>? = null
    private var hasContactPicker: Boolean? = null
    private var isReplyToEncryptedMessage = false

    private var lastFocusedType = RecipientType.TO
    private var currentCryptoMode = CryptoMode.NO_CHOICE

    var isForceTextMessageFormat = false
        private set

    var currentCachedCryptoStatus: ComposeCryptoStatus? = null
        private set

    val toAddresses: List<Address>
        get() = recipientMvpView.toAddresses

    val ccAddresses: List<Address>
        get() = recipientMvpView.ccAddresses

    val bccAddresses: List<Address>
        get() = recipientMvpView.bccAddresses

    private val allRecipients: List<Recipient>
        get() = with(recipientMvpView) { toRecipients + ccRecipients + bccRecipients }

    init {
        recipientMvpView.setPresenter(this)
        recipientMvpView.setLoaderManager(loaderManager)

        onSwitchAccount(account)
    }

    fun checkRecipientsOkForSending(): Boolean {
        recipientMvpView.recipientToTryPerformCompletion()
        recipientMvpView.recipientCcTryPerformCompletion()
        recipientMvpView.recipientBccTryPerformCompletion()

        if (recipientMvpView.recipientToHasUncompletedText()) {
            recipientMvpView.showToUncompletedError()
            return true
        }

        if (recipientMvpView.recipientCcHasUncompletedText()) {
            recipientMvpView.showCcUncompletedError()
            return true
        }

        if (recipientMvpView.recipientBccHasUncompletedText()) {
            recipientMvpView.showBccUncompletedError()
            return true
        }

        if (toAddresses.isEmpty() && ccAddresses.isEmpty() && bccAddresses.isEmpty()) {
            recipientMvpView.showNoRecipientsError()
            return true
        }

        return false
    }

    fun initFromReplyToMessage(message: Message?, isReplyAll: Boolean) {
        val replyToAddresses = if (isReplyAll) {
            replyToParser.getRecipientsToReplyAllTo(message, account)
        } else {
            replyToParser.getRecipientsToReplyTo(message, account)
        }

        addToAddresses(*replyToAddresses.to)
        addCcAddresses(*replyToAddresses.cc)

        val shouldSendAsPgpInline = composePgpInlineDecider.shouldReplyInline(message)
        if (shouldSendAsPgpInline) {
            isForceTextMessageFormat = true
        }

        isReplyToEncryptedMessage = composePgpEnableByDefaultDecider.shouldEncryptByDefault(message)
    }

    fun initFromTrustIdAction(trustId: String?) {
        addToAddresses(*Address.parse(trustId))
        currentCryptoMode = CryptoMode.CHOICE_ENABLED
    }

    fun initFromMailto(mailTo: MailTo) {
        addToAddresses(*mailTo.to)
        addCcAddresses(*mailTo.cc)
        addBccAddresses(*mailTo.bcc)
    }

    fun initFromSendOrViewIntent(intent: Intent) {
        val toAddresses = intent.getStringArrayExtra(Intent.EXTRA_EMAIL)?.toAddressArray()
        val ccAddresses = intent.getStringArrayExtra(Intent.EXTRA_CC)?.toAddressArray()
        val bccAddresses = intent.getStringArrayExtra(Intent.EXTRA_BCC)?.toAddressArray()

        if (toAddresses != null) {
            addToAddresses(*toAddresses)
        }

        if (ccAddresses != null) {
            addCcAddresses(*ccAddresses)
        }

        if (bccAddresses != null) {
            addBccAddresses(*bccAddresses)
        }
    }

    fun onRestoreInstanceState(savedInstanceState: Bundle) {
        recipientMvpView.setCcVisibility(savedInstanceState.getBoolean(STATE_KEY_CC_SHOWN))
        recipientMvpView.setBccVisibility(savedInstanceState.getBoolean(STATE_KEY_BCC_SHOWN))
        lastFocusedType = RecipientType.valueOf(savedInstanceState.getString(STATE_KEY_LAST_FOCUSED_TYPE)!!)
        currentCryptoMode = CryptoMode.valueOf(savedInstanceState.getString(STATE_KEY_CURRENT_CRYPTO_MODE)!!)
        isForceTextMessageFormat = savedInstanceState.getBoolean(STATE_KEY_CRYPTO_ENABLE_PGP_INLINE)

        updateRecipientExpanderVisibility()
    }

    fun onSaveInstanceState(outState: Bundle) {
        outState.putBoolean(STATE_KEY_CC_SHOWN, recipientMvpView.isCcVisible)
        outState.putBoolean(STATE_KEY_BCC_SHOWN, recipientMvpView.isBccVisible)
        outState.putString(STATE_KEY_LAST_FOCUSED_TYPE, lastFocusedType.toString())
        outState.putString(STATE_KEY_CURRENT_CRYPTO_MODE, currentCryptoMode.toString())
        outState.putBoolean(STATE_KEY_CRYPTO_ENABLE_PGP_INLINE, isForceTextMessageFormat)
    }

    fun initFromDraftMessage(message: Message) {
        initRecipientsFromDraftMessage(message)

        val draftStateHeader = message.getHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER)
        if (draftStateHeader.size == 1) {
            initEncryptionStateFromDraftStateHeader(draftStateHeader.first())
        } else {
            initPgpInlineFromDraftMessage(message)
        }
    }

    private fun initEncryptionStateFromDraftStateHeader(headerValue: String) {
        val autocryptDraftStateHeader = draftStateHeaderParser.parseAutocryptDraftStateHeader(headerValue)
        if (autocryptDraftStateHeader != null) {
            initEncryptionStateFromDraftStateHeader(autocryptDraftStateHeader)
        }
    }

    private fun initRecipientsFromDraftMessage(message: Message) {
        addToAddresses(*message.getRecipients(RecipientType.TO))
        addCcAddresses(*message.getRecipients(RecipientType.CC))
        addBccAddresses(*message.getRecipients(RecipientType.BCC))
    }

    private fun initEncryptionStateFromDraftStateHeader(draftState: AutocryptDraftStateHeader) {
        isForceTextMessageFormat = draftState.isPgpInline
        isReplyToEncryptedMessage = draftState.isReply

        if (!draftState.isByChoice) {
            // TODO if it's not by choice, we're going with our defaults. should we do something here if those differ?
            return
        }

        currentCryptoMode = when {
            draftState.isSignOnly -> CryptoMode.SIGN_ONLY
            draftState.isEncrypt -> CryptoMode.CHOICE_ENABLED
            else -> CryptoMode.CHOICE_DISABLED
        }
    }

    private fun initPgpInlineFromDraftMessage(message: Message) {
        isForceTextMessageFormat = message.isSet(Flag.X_DRAFT_OPENPGP_INLINE)
    }

    private fun addToAddresses(vararg toAddresses: Address) {
        addRecipientsFromAddresses(RecipientType.TO, *toAddresses)
    }

    private fun addCcAddresses(vararg ccAddresses: Address) {
        if (ccAddresses.isNotEmpty()) {
            addRecipientsFromAddresses(RecipientType.CC, *ccAddresses)
            recipientMvpView.setCcVisibility(true)
            updateRecipientExpanderVisibility()
        }
    }

    private fun addBccAddresses(vararg bccRecipients: Address) {
        if (bccRecipients.isNotEmpty()) {
            addRecipientsFromAddresses(RecipientType.BCC, *bccRecipients)
            recipientMvpView.setBccVisibility(true)
            updateRecipientExpanderVisibility()
        }
    }

    private fun addAlwaysBcc() {
        val alwaysBccAddresses = Address.parse(account.alwaysBcc)
        this.alwaysBccAddresses = alwaysBccAddresses
        if (alwaysBccAddresses.isEmpty()) return

        object : RecipientLoader(context, account.openPgpProvider, *alwaysBccAddresses) {
            override fun deliverResult(result: List<Recipient>?) {
                val recipientArray = result!!.toTypedArray()
                recipientMvpView.silentlyAddBccAddresses(*recipientArray)

                stopLoading()
                abandon()
            }
        }.startLoading()
    }

    private fun removeAlwaysBcc() {
        alwaysBccAddresses?.let { alwaysBccAddresses ->
            recipientMvpView.silentlyRemoveBccAddresses(alwaysBccAddresses)
        }
    }

    fun onPrepareOptionsMenu(menu: Menu) {
        val currentCryptoStatus = currentCachedCryptoStatus

        if (currentCryptoStatus != null && currentCryptoStatus.isProviderStateOk()) {
            val isEncrypting = currentCryptoStatus.isEncryptionEnabled
            menu.findItem(R.id.openpgp_encrypt_enable).isVisible = !isEncrypting
            menu.findItem(R.id.openpgp_encrypt_disable).isVisible = isEncrypting

            val showSignOnly = !account.isOpenPgpHideSignOnly
            val isSignOnly = currentCryptoStatus.isSignOnly
            menu.findItem(R.id.openpgp_sign_only).isVisible = showSignOnly && !isSignOnly
            menu.findItem(R.id.openpgp_sign_only_disable).isVisible = showSignOnly && isSignOnly

            val pgpInlineModeEnabled = currentCryptoStatus.isPgpInlineModeEnabled
            val showPgpInlineEnable = (isEncrypting || isSignOnly) && !pgpInlineModeEnabled
            menu.findItem(R.id.openpgp_inline_enable).isVisible = showPgpInlineEnable
            menu.findItem(R.id.openpgp_inline_disable).isVisible = pgpInlineModeEnabled
        } else {
            menu.findItem(R.id.openpgp_inline_enable).isVisible = false
            menu.findItem(R.id.openpgp_inline_disable).isVisible = false
            menu.findItem(R.id.openpgp_encrypt_enable).isVisible = false
            menu.findItem(R.id.openpgp_encrypt_disable).isVisible = false
            menu.findItem(R.id.openpgp_sign_only).isVisible = false
            menu.findItem(R.id.openpgp_sign_only_disable).isVisible = false
        }

        menu.findItem(R.id.add_from_contacts).isVisible = hasContactPermission() && hasContactPicker()
    }

    fun onSwitchAccount(account: Account) {
        this.account = account

        if (account.isAlwaysShowCcBcc) {
            recipientMvpView.setCcVisibility(true)
            recipientMvpView.setBccVisibility(true)
            updateRecipientExpanderVisibility()
        }

        removeAlwaysBcc()
        addAlwaysBcc()

        val openPgpProvider = account.openPgpProvider
        recipientMvpView.setCryptoProvider(openPgpProvider)
        openPgpApiManager.setOpenPgpProvider(openPgpProvider, openPgpCallback)
    }

    fun onSwitchIdentity(identity: Identity) {
        // TODO decide what actually to do on identity switch?
        asyncUpdateCryptoStatus()
    }

    fun onClickToLabel() {
        recipientMvpView.requestFocusOnToField()
    }

    fun onClickCcLabel() {
        recipientMvpView.requestFocusOnCcField()
    }

    fun onClickBccLabel() {
        recipientMvpView.requestFocusOnBccField()
    }

    fun onClickRecipientExpander() {
        recipientMvpView.setCcVisibility(true)
        recipientMvpView.setBccVisibility(true)
        updateRecipientExpanderVisibility()
    }

    private fun hideEmptyExtendedRecipientFields() {
        if (recipientMvpView.ccAddresses.isEmpty()) {
            recipientMvpView.setCcVisibility(false)
            if (lastFocusedType == RecipientType.CC) {
                lastFocusedType = RecipientType.TO
            }
        }

        if (recipientMvpView.bccAddresses.isEmpty()) {
            recipientMvpView.setBccVisibility(false)
            if (lastFocusedType == RecipientType.BCC) {
                lastFocusedType = RecipientType.TO
            }
        }

        updateRecipientExpanderVisibility()
    }

    private fun updateRecipientExpanderVisibility() {
        val notBothAreVisible = !(recipientMvpView.isCcVisible && recipientMvpView.isBccVisible)
        recipientMvpView.setRecipientExpanderVisibility(notBothAreVisible)
    }

    fun asyncUpdateCryptoStatus() {
        currentCachedCryptoStatus = null

        val openPgpProviderState = openPgpApiManager.openPgpProviderState
        var accountCryptoKey: Long? = account.openPgpKey
        if (accountCryptoKey == Account.NO_OPENPGP_KEY) {
            accountCryptoKey = null
        }

        val composeCryptoStatus = ComposeCryptoStatus(
            openPgpProviderState = openPgpProviderState,
            openPgpKeyId = accountCryptoKey,
            recipientAddresses = allRecipients,
            isPgpInlineModeEnabled = isForceTextMessageFormat,
            isSenderPreferEncryptMutual = account.autocryptPreferEncryptMutual,
            isReplyToEncrypted = isReplyToEncryptedMessage,
            isEncryptAllDrafts = account.isOpenPgpEncryptAllDrafts,
            isEncryptSubject = account.isOpenPgpEncryptSubject,
            cryptoMode = currentCryptoMode
        )

        if (openPgpProviderState != OpenPgpProviderState.OK) {
            currentCachedCryptoStatus = composeCryptoStatus
            redrawCachedCryptoStatusIcon()
            return
        }

        val recipientAddresses = composeCryptoStatus.recipientAddressesAsArray
        object : AsyncTask<Void?, Void?, RecipientAutocryptStatus?>() {
            override fun doInBackground(vararg params: Void?): RecipientAutocryptStatus? {
                val openPgpApi = openPgpApiManager.openPgpApi ?: return null
                return autocryptStatusInteractor.retrieveCryptoProviderRecipientStatus(openPgpApi, recipientAddresses)
            }

            override fun onPostExecute(recipientAutocryptStatus: RecipientAutocryptStatus?) {
                currentCachedCryptoStatus = if (recipientAutocryptStatus != null) {
                    composeCryptoStatus.withRecipientAutocryptStatus(recipientAutocryptStatus)
                } else {
                    composeCryptoStatus
                }

                redrawCachedCryptoStatusIcon()
            }
        }.execute()
    }

    private fun redrawCachedCryptoStatusIcon() {
        val cryptoStatus = checkNotNull(currentCachedCryptoStatus) { "must have cached crypto status to redraw it!" }

        recipientMvpView.setRecipientTokensShowCryptoEnabled(cryptoStatus.isEncryptionEnabled)
        recipientMvpView.showCryptoStatus(cryptoStatus.displayType)
        recipientMvpView.showCryptoSpecialMode(cryptoStatus.specialModeDisplayType)
    }

    fun onToTokenAdded() {
        asyncUpdateCryptoStatus()
    }

    fun onToTokenRemoved() {
        asyncUpdateCryptoStatus()
    }

    fun onToTokenChanged() {
        asyncUpdateCryptoStatus()
    }

    fun onCcTokenAdded() {
        asyncUpdateCryptoStatus()
    }

    fun onCcTokenRemoved() {
        asyncUpdateCryptoStatus()
    }

    fun onCcTokenChanged() {
        asyncUpdateCryptoStatus()
    }

    fun onBccTokenAdded() {
        asyncUpdateCryptoStatus()
    }

    fun onBccTokenRemoved() {
        asyncUpdateCryptoStatus()
    }

    fun onBccTokenChanged() {
        asyncUpdateCryptoStatus()
    }

    fun onCryptoModeChanged(cryptoMode: CryptoMode) {
        currentCryptoMode = cryptoMode
        asyncUpdateCryptoStatus()
    }

    fun onCryptoPgpInlineChanged(enablePgpInline: Boolean) {
        isForceTextMessageFormat = enablePgpInline
        asyncUpdateCryptoStatus()
    }

    private fun addRecipientsFromAddresses(recipientType: RecipientType, vararg addresses: Address) {
        object : RecipientLoader(context, account.openPgpProvider, *addresses) {
            override fun deliverResult(result: List<Recipient>?) {
                val recipientArray = result!!.toTypedArray()
                recipientMvpView.addRecipients(recipientType, *recipientArray)

                stopLoading()
                abandon()
            }
        }.startLoading()
    }

    private fun addRecipientFromContactUri(recipientType: RecipientType, uri: Uri?) {
        object : RecipientLoader(context, account.openPgpProvider, uri, false) {
            override fun deliverResult(result: List<Recipient>?) {
                // TODO handle multiple available mail addresses for a contact?
                if (result!!.isEmpty()) {
                    recipientMvpView.showErrorContactNoAddress()
                    return
                }

                val recipient = result[0]
                recipientMvpView.addRecipients(recipientType, recipient)

                stopLoading()
                abandon()
            }
        }.startLoading()
    }

    fun onToFocused() {
        lastFocusedType = RecipientType.TO
    }

    fun onCcFocused() {
        lastFocusedType = RecipientType.CC
    }

    fun onBccFocused() {
        lastFocusedType = RecipientType.BCC
    }

    fun onMenuAddFromContacts() {
        val requestCode = lastFocusedType.toRequestCode()
        recipientMvpView.showContactPicker(requestCode)
    }

    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        when (requestCode) {
            CONTACT_PICKER_TO, CONTACT_PICKER_CC, CONTACT_PICKER_BCC -> {
                if (resultCode != Activity.RESULT_OK || data == null) return

                val recipientType = requestCode.toRecipientType()
                addRecipientFromContactUri(recipientType, data.data)
            }
            OPENPGP_USER_INTERACTION -> {
                openPgpApiManager.onUserInteractionResult()
            }
            REQUEST_CODE_AUTOCRYPT -> {
                asyncUpdateCryptoStatus()
            }
        }
    }

    fun onNonRecipientFieldFocused() {
        if (!account.isAlwaysShowCcBcc) {
            hideEmptyExtendedRecipientFields()
        }
    }

    fun onClickCryptoStatus() {
        when (openPgpApiManager.openPgpProviderState) {
            OpenPgpProviderState.UNCONFIGURED -> {
                Timber.e("click on crypto status while unconfigured - this should not really happen?!")
            }
            OpenPgpProviderState.OK -> {
                toggleEncryptionState(false)
            }
            OpenPgpProviderState.UI_REQUIRED -> {
                // TODO show openpgp settings
                val pendingIntent = openPgpApiManager.userInteractionPendingIntent
                recipientMvpView.launchUserInteractionPendingIntent(pendingIntent, OPENPGP_USER_INTERACTION)
            }
            OpenPgpProviderState.UNINITIALIZED, OpenPgpProviderState.ERROR -> {
                openPgpApiManager.refreshConnection()
            }
        }
    }

    private fun toggleEncryptionState(showGotIt: Boolean) {
        val currentCryptoStatus = currentCachedCryptoStatus
        if (currentCryptoStatus == null) {
            Timber.e("click on crypto status while crypto status not available - should not really happen?!")
            return
        }

        if (currentCryptoStatus.isEncryptionEnabled && !currentCryptoStatus.allRecipientsCanEncrypt()) {
            recipientMvpView.showOpenPgpEnabledErrorDialog(false)
            return
        }

        if (currentCryptoMode == CryptoMode.SIGN_ONLY) {
            recipientMvpView.showErrorIsSignOnly()
            return
        }

        val isEncryptOnNoChoice = currentCryptoStatus.canEncryptAndIsMutualDefault() ||
            currentCryptoStatus.isReplyToEncrypted

        if (currentCryptoMode == CryptoMode.NO_CHOICE) {
            if (currentCryptoStatus.hasAutocryptPendingIntent()) {
                recipientMvpView.launchUserInteractionPendingIntent(
                    currentCryptoStatus.autocryptPendingIntent, REQUEST_CODE_AUTOCRYPT
                )
            } else if (isEncryptOnNoChoice) {
                // TODO warning dialog if we override, especially from reply!
                onCryptoModeChanged(CryptoMode.CHOICE_DISABLED)
            } else {
                onCryptoModeChanged(CryptoMode.CHOICE_ENABLED)
                if (showGotIt) {
                    recipientMvpView.showOpenPgpEncryptExplanationDialog()
                }
            }
        } else if (currentCryptoMode == CryptoMode.CHOICE_DISABLED && !isEncryptOnNoChoice) {
            onCryptoModeChanged(CryptoMode.CHOICE_ENABLED)
        } else {
            onCryptoModeChanged(CryptoMode.NO_CHOICE)
        }
    }

    /**
     * Does the device actually have a Contacts application suitable for picking a contact.
     * As hard as it is to believe, some vendors ship without it.
     */
    private fun hasContactPicker(): Boolean {
        return hasContactPicker ?: isContactPickerAvailable().also { hasContactPicker = it }
    }

    private fun isContactPickerAvailable(): Boolean {
        val contacts = Contacts.getInstance(context)
        val resolveInfoList = context.packageManager.queryIntentActivities(contacts.contactPickerIntent(), 0)
        return resolveInfoList.isNotEmpty()
    }

    private fun hasContactPermission(): Boolean {
        val permissionState = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
        return permissionState == PackageManager.PERMISSION_GRANTED
    }

    fun showPgpSendError(sendErrorState: SendErrorState) {
        when (sendErrorState) {
            SendErrorState.ENABLED_ERROR -> recipientMvpView.showOpenPgpEnabledErrorDialog(false)
            SendErrorState.PROVIDER_ERROR -> recipientMvpView.showErrorOpenPgpConnection()
            SendErrorState.KEY_CONFIG_ERROR -> recipientMvpView.showErrorNoKeyConfigured()
            else -> throw AssertionError("not all error states handled, this is a bug!")
        }
    }

    fun showPgpAttachError(attachErrorState: AttachErrorState) {
        when (attachErrorState) {
            AttachErrorState.IS_INLINE -> recipientMvpView.showErrorInlineAttach()
            else -> throw AssertionError("not all error states handled, this is a bug!")
        }
    }

    fun builderSetProperties(messageBuilder: MessageBuilder) {
        require(messageBuilder !is PgpMessageBuilder) {
            "PpgMessageBuilder must be called with ComposeCryptoStatus argument!"
        }

        messageBuilder.setTo(toAddresses)
        messageBuilder.setCc(ccAddresses)
        messageBuilder.setBcc(bccAddresses)
    }

    fun builderSetProperties(pgpMessageBuilder: PgpMessageBuilder, cryptoStatus: ComposeCryptoStatus) {
        pgpMessageBuilder.setTo(toAddresses)
        pgpMessageBuilder.setCc(ccAddresses)
        pgpMessageBuilder.setBcc(bccAddresses)
        pgpMessageBuilder.setOpenPgpApi(openPgpApiManager.openPgpApi)
        pgpMessageBuilder.setCryptoStatus(cryptoStatus)
    }

    fun onMenuSetPgpInline(enablePgpInline: Boolean) {
        onCryptoPgpInlineChanged(enablePgpInline)

        if (enablePgpInline) {
            val shouldShowPgpInlineDialog = checkAndIncrementPgpInlineDialogCounter()
            if (shouldShowPgpInlineDialog) {
                recipientMvpView.showOpenPgpInlineDialog(true)
            }
        }
    }

    fun onMenuSetSignOnly(enableSignOnly: Boolean) {
        if (enableSignOnly) {
            onCryptoModeChanged(CryptoMode.SIGN_ONLY)

            val shouldShowPgpSignOnlyDialog = checkAndIncrementPgpSignOnlyDialogCounter()
            if (shouldShowPgpSignOnlyDialog) {
                recipientMvpView.showOpenPgpSignOnlyDialog(true)
            }
        } else {
            onCryptoModeChanged(CryptoMode.NO_CHOICE)
        }
    }

    fun onMenuToggleEncryption() {
        toggleEncryptionState(true)
    }

    fun onCryptoPgpClickDisable() {
        onCryptoModeChanged(CryptoMode.CHOICE_DISABLED)
    }

    fun onCryptoPgpSignOnlyDisabled() {
        onCryptoPgpInlineChanged(false)
        onCryptoModeChanged(CryptoMode.NO_CHOICE)
    }

    private fun checkAndIncrementPgpInlineDialogCounter(): Boolean {
        val pgpInlineDialogCounter = K9.pgpInlineDialogCounter
        if (pgpInlineDialogCounter < PGP_DIALOG_DISPLAY_THRESHOLD) {
            K9.pgpInlineDialogCounter = pgpInlineDialogCounter + 1
            K9.saveSettingsAsync()
            return true
        }

        return false
    }

    private fun checkAndIncrementPgpSignOnlyDialogCounter(): Boolean {
        val pgpSignOnlyDialogCounter = K9.pgpSignOnlyDialogCounter
        if (pgpSignOnlyDialogCounter < PGP_DIALOG_DISPLAY_THRESHOLD) {
            K9.pgpSignOnlyDialogCounter = pgpSignOnlyDialogCounter + 1
            K9.saveSettingsAsync()
            return true
        }

        return false
    }

    fun onClickCryptoSpecialModeIndicator() {
        when {
            currentCryptoMode == CryptoMode.SIGN_ONLY -> {
                recipientMvpView.showOpenPgpSignOnlyDialog(false)
            }
            isForceTextMessageFormat -> {
                recipientMvpView.showOpenPgpInlineDialog(false)
            }
            else -> {
                error("This icon should not be clickable while no special mode is active!")
            }
        }
    }

    private val openPgpCallback = object : OpenPgpApiManagerCallback {
        override fun onOpenPgpProviderStatusChanged() {
            if (openPgpApiManager.openPgpProviderState == OpenPgpProviderState.UI_REQUIRED) {
                recipientMvpView.showErrorOpenPgpUserInteractionRequired()
            }

            asyncUpdateCryptoStatus()
        }

        override fun onOpenPgpProviderError(error: OpenPgpProviderError) {
            when (error) {
                OpenPgpProviderError.ConnectionLost -> openPgpApiManager.refreshConnection()
                OpenPgpProviderError.VersionIncompatible -> recipientMvpView.showErrorOpenPgpIncompatible()
                OpenPgpProviderError.ConnectionFailed -> recipientMvpView.showErrorOpenPgpConnection()
                else -> recipientMvpView.showErrorOpenPgpConnection()
            }
        }
    }

    private fun Array<String>.toAddressArray(): Array<Address> {
        return flatMap { addressString ->
            Address.parseUnencoded(addressString).toList()
        }.toTypedArray()
    }

    private fun RecipientType.toRequestCode(): Int = when (this) {
        RecipientType.TO -> CONTACT_PICKER_TO
        RecipientType.CC -> CONTACT_PICKER_CC
        RecipientType.BCC -> CONTACT_PICKER_BCC
        else -> throw AssertionError("Unhandled case: $this")
    }

    private fun Int.toRecipientType(): RecipientType = when (this) {
        CONTACT_PICKER_TO -> RecipientType.TO
        CONTACT_PICKER_CC -> RecipientType.CC
        CONTACT_PICKER_BCC -> RecipientType.BCC
        else -> throw AssertionError("Unhandled case: $this")
    }

    enum class CryptoMode {
        SIGN_ONLY, NO_CHOICE, CHOICE_DISABLED, CHOICE_ENABLED
    }
}
